#pragma once
#ifndef __GLF_DEBUGGER_MEMORY_MONITOR_H__
#define __GLF_DEBUGGER_MEMORY_MONITOR_H__

#include <glf/config.h>

#if GLF_ENABLE_DEBUGGER

#include <glf/debugger/debugger.h>
#include <map>
#include <vector>
#include <stdlib.h>

namespace glf
{
namespace debugger
{
class MemoryContainer
{
public:
	static const uint MAX_SNAPSHOT_STACK_SIZE = 32;

	struct SAlloc
	{
		SAlloc() :
			mSContext(0),
			mSize(0),
			mFileName(""),
			mLine(0),
			mRefCount(0),
			mTimeStamp(0),
			mCallStackHash(0)
		{
		}

		uint mSContext;
		uint mSize;
		const char* mFileName;
		uint mLine;
		uint mRefCount;
		uint64 mTimeStamp;
		unsigned long mCallStackHash;
	};

	typedef std::map<void*, SAlloc, std::less<void*>, DebuggerAllocator<std::pair<void* const, SAlloc> > > AllocMap;

	struct SStats
	{
		SStats() :
			AllocationCount(0),
			UsedMemory(0),
			PeakMemory(0)
		{
		}

		uint AllocationCount;
		uint UsedMemory;
		uint PeakMemory;
	};

	typedef std::vector<SStats, DebuggerAllocator<SStats> > StatsVector;

	struct SSharedContext
	{
		SSharedContext(const char* name) :
			Name(name)
		{
		}

		inline SStats& getStats(uint allocatorID)
		{
			if(Stats.size() <= allocatorID)
				Stats.resize(allocatorID + 1);
			return Stats[allocatorID];
		}

		stringdbg Name;
		StatsVector Stats;
	};

	typedef std::vector<SSharedContext, DebuggerAllocator<SSharedContext> > SharedContextVector;

	struct SContext
	{
		SContext(uint index, uint sharedContextIndex) :
			Index(index),
			SharedContextIndex(sharedContextIndex)
		{
		}

		inline SStats& getStats(uint allocatorID)
		{
			if(Stats.size() <= allocatorID)
				Stats.resize(allocatorID + 1);
			return Stats[allocatorID];
		}

		static void* operator new(size_t size)
		{
			return Alloc(size);
		}

		static void operator delete(void *p)
		{
			Free(p);
		}

		typedef std::map<const char*, SContext*, std::less<const char*>, DebuggerAllocator<std::pair<const char* const, SContext*> > > ChildContextMap;

		// context
		const uint Index;
		const uint SharedContextIndex;
		ChildContextMap ChildContexts;
		StatsVector Stats;
	};

	typedef std::vector<SContext*, DebuggerAllocator<SContext*> > ContextVector;

	struct SThreadContextStack
	{
		ContextVector Stack;
	};
	
	typedef std::map<uint, SThreadContextStack, std::less<const uint>, DebuggerAllocator<std::pair<const uint, SThreadContextStack> > > SThreadContextStackMap;

	struct SMemoryAllocator
	{
		SMemoryAllocator(const char* name) :
			Name(name),
			Capacity(0)
		{
		}

		static void* operator new(size_t size)
		{
			return Alloc(size);
		}

		static void operator delete(void *p)
		{
			Free(p);
		}

		AllocMap Allocations;
		stringdbg Name;
		uint Capacity;
	};

	typedef std::vector<SMemoryAllocator*, DebuggerAllocator<SMemoryAllocator*> > AllocatorVector;

	struct CallStackContext
	{
		CallStackContext(unsigned long address = 0, int index = 0, int sharedIndex = 0, CallStackContext* parent = NULL) :
			mAddress(address),
			mIndex(index),
			mSharedIndex(sharedIndex),
			mParent(parent)
		{
		}

		static void* operator new(size_t size)
		{
			return Alloc(size);
		}

		static void operator delete(void *p)
		{
			Free(p);
		}

		typedef std::map<const char*, CallStackContext*, std::less<const char*>, DebuggerAllocator<std::pair<const char* const, CallStackContext*> > > ChildContextMap;

		unsigned long mAddress;
		int mIndex;
		int mSharedIndex;
		CallStackContext* mParent;
		SStats mStats;
		ChildContextMap mChildContextMap;
	};

	const char* GetString(const stringdbg& str)
	{
		StringMap::const_iterator it = mStringMap.find(str);
		if(it == mStringMap.end())
		{
			std::pair<stringdbg, stringdbg> entry(str, str);
			it = mStringMap.insert(mStringMap.begin(), entry);
		}
		return it->second.c_str();
	}

	typedef std::map<stringdbg, stringdbg, std::less<stringdbg>, DebuggerAllocator<std::pair<const stringdbg, stringdbg> > > StringMap;
	typedef std::vector<CallStackContext*, DebuggerAllocator<CallStackContext*> > CallStackContextVector;
	typedef std::map<uint, CallStackContext*, std::less<const uint>, DebuggerAllocator<std::pair<const uint, CallStackContext*> > > CallStackMap;
	typedef std::map<uint, const char*, std::less<uint>, DebuggerAllocator<std::pair<uint, const char*> > > AddressMap;
	typedef std::map<const char*, int, std::less<const char*>, DebuggerAllocator<std::pair<const char* const, int> > > StringIndexMap;

	AllocatorVector Allocators;
	SharedContextVector SharedContexts;
	ContextVector Contexts;
	SThreadContextStackMap ThreadContextStacks;

	StringMap mStringMap;
	AddressMap mAddressMap;
	CallStackMap mAllocationCallStackMap;
	CallStackContext mRootCallStackContext;
	CallStackContextVector mCallStackContextVector;
	SharedContextVector mSharedCallStackContextVector;
	StringIndexMap mSharedCallStackStringIndexMap;
};

class MemoryMonitor : protected MemoryContainer, public Module
{
	GLF_DECLARE_SINGLETON_WITH_DEP(MemoryMonitor, Debugger)
public:
	static void RegisterAllocation(void* ptr, size_t size, const char* fileName, uint line, uint allocatorID = SYSTEM_ALLOCATOR)
	{
		if( GetInstance() )	
		{
			GetInstance()->RegisterAllocationInternal(ptr, size, fileName, line, allocatorID);
		}
	}
	
	static void UnregisterAllocation(void* ptr, uint allocatorID = SYSTEM_ALLOCATOR)
	{
		if( GetInstance() )	
		{
			GetInstance()->UnregisterAllocationInternal(ptr, allocatorID);
		}		
	}
	
	static void SetRecordCallStack(bool enabled)
	{
		GetInstance()->RecordCallStack = enabled;
	}
	
	static void PushMemoryContext(const char* name)
	{
		GetInstance()->PushContext(name);
	}

	static void PopMemoryContext()
	{
		GetInstance()->PopContext();
	}

	static uint RegisterAllocator(const char* name)
	{
		return GetInstance()->CreateAllocator(name);
	}

	static void SetAllocatorCapacity(uint allocatorID, uint capacity)
	{
		GLF_ASSERT(allocatorID != SYSTEM_ALLOCATOR);
		GetInstance()->Allocators[allocatorID]->Capacity = capacity;
	}

	static void CreateSnapshot(const char* name)
	{
		GetInstance()->SendSnapshot(name);
	}

	static void CreateSnapshotLeaks(const char* name)
	{
		GetInstance()->SendMemoryLeaks(name);
	}

	static uint GetTotalAllocated(uint allocatorID = SYSTEM_ALLOCATOR)
	{
		return 	GetInstance()->GetTotalAllocatedInternal(allocatorID);
	}

	static uint64 GetBaseTime()
	{
		return GetInstance()->BaseTime;
	}

	virtual void UpdateFrame();
	virtual void Parse(int type, PacketReader& in);
	virtual void DebuggerDestroyed();

	static const uint SYSTEM_ALLOCATOR = 0;

private:
	MemoryMonitor();

	enum MessageType
	{
		// Debugger to Glf
		MESSAGE_REQUEST_REFRESH,
		MESSAGE_REQUEST_SNAPSHOT,
		MESSAGE_SET_OPTIONS,
		MESSAGE_START_ACTIVITY,
		MESSAGE_STOP_ACTIVITY,
		MESSAGE_REQUEST_CALLSTACK,

		// Glf to Debugger
		MESSAGE_REFRESH,
		MESSAGE_SNAPSHOT,
		MESSAGE_ACTIVITY
	};

	struct SEvent
	{
		SEvent(bool allocation, void* address, uint allocatorID, SAlloc& alloc) :
			Allocation(allocation),
			Address(address),
			AllocatorID(allocatorID),
			Alloc(alloc)
		{
		}

		bool Allocation;
		void* Address;
		uint AllocatorID;
		SAlloc Alloc;
	};

	typedef std::vector<SEvent, DebuggerAllocator<SEvent> > EventVector;
	
	virtual ~MemoryMonitor();

	void RegisterAllocationInternal(void* ptr, size_t size, const char* fileName, uint line, uint allocatorID);
	void UnregisterAllocationInternal(void* ptr, uint allocatorID);
	uint GetTotalAllocatedInternal(uint allocatorID);

	SContext* CreateContext(const char* name);
	void PushContext(const char* name);
	void PopContext();

	uint CreateAllocator(const char* name);
	SThreadContextStack& GetThreadContextStack();

	void SendSnapshot(const char* name);
	void SendMemoryMonitorRefresh();
	void SendMemoryMonitorSnapshot(MemoryContainer* mc, const char* name, bool allocations, bool callStacks);
	void SendMemoryLeaks(const char* name);
	void SendMemoryMonitorActivity();
	void SendMemoryCallStack(unsigned long hash);
	void ResetPerformanceCounters();

	static void InitBaseTime(MemoryMonitor* refThis);

	static void InitBaseTime();
	static void ReleaseInstance();
	static void Close();

	static void* operator new(size_t size)
	{
		return Alloc(size);
	}

	static void operator delete(void *p)
	{
		Free(p);
	}

	bool RecordCallStack;
	uint64 BaseTime;

	// activity recording
	EventVector ActivityEvents;
	bool RecordActivity;

	// perf counters
	int FrameAllocationCount;
	int FrameDeallocationCount;
	int FrameAllocatedHeap;
	int FrameDeallocatedHeap;

	friend class Debugger;
	friend class MemoryMonitorCleaner;
	friend class MemoryContext;
	friend class MemoryLeakDetector;
};

class MemoryLeakDetector : public MemoryContainer
{
public:
	enum State
	{
		STATE_INVALID = -1,
		STATE_ALLOCATION_BASE = 0
		// 1..7 allocation base+n
	};

	struct Range
	{
		void* mBegin;
		void* mEnd;
	};

	typedef std::vector<char, DebuggerAllocator<char> > AllocVector;

	MemoryLeakDetector(MemoryMonitor* monitor = MemoryMonitor::GetInstance());
	void Scan();

protected:
	void CopyContexts();
	void InitRanges();
	void ScanRange(SMemoryAllocator& allocator, void* startAddress, void* stopAddress);

	MemoryMonitor* mMonitor;
	AllocVector mAllocVector;
	Range mStaticRange;
	Range mHeapRange;
};

class MemoryContext
{
public:
	MemoryContext(const char* name)
	{
		MemoryMonitor::GetInstance()->PushContext(name);
	}

	~MemoryContext()
	{
		MemoryMonitor::GetInstance()->PopContext();
	}
};

} // end namespace debugger
} // end namespace glf

#endif // GLF_ENABLE_DEBUGGER

#endif
